﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Numerics;
using System.Reflection;
using Amm.DapperExtensions.Attributes;
using DapperExtensions;

namespace Amm.DapperExtensions.Mapper
{
    public interface IClassMapper
    {
        string SchemaName { get; }
        string TableName { get; }
        IList<IPropertyMap> Properties { get; }
        Type EntityType { get; }
    }

    /// <summary>
    /// Maps an entity to a table through a collection of property maps.
    /// </summary>
    public abstract class ClassMapper<T> : IClassMapper<T> where T : class
    {
        /// <summary>
        /// Gets or sets the schema to use when referring to the corresponding table name in the database.
        /// </summary>
        public string SchemaName { get; protected set; }

        /// <summary>
        /// Gets or sets the table to use in the database.
        /// </summary>
        public string TableName { get; protected set; }

        /// <summary>
        /// A collection of properties that will map to columns in the database table.
        /// </summary>
        public IList<IPropertyMap> Properties { get; }

        public Type EntityType => typeof(T);

        protected ClassMapper()
        {
            PropertyTypeKeyTypeMapping = new Dictionary<Type, KeyType>
                                             {
                                                 { typeof(byte), KeyType.Identity }, { typeof(byte?), KeyType.Identity },
                                                 { typeof(sbyte), KeyType.Identity }, { typeof(sbyte?), KeyType.Identity },
                                                 { typeof(short), KeyType.Identity }, { typeof(short?), KeyType.Identity },
                                                 { typeof(ushort), KeyType.Identity }, { typeof(ushort?), KeyType.Identity },
                                                 { typeof(int), KeyType.Identity }, { typeof(int?), KeyType.Identity },
                                                 { typeof(uint), KeyType.Identity}, { typeof(uint?), KeyType.Identity },
                                                 { typeof(long), KeyType.Identity }, { typeof(long?), KeyType.Identity },
                                                 { typeof(ulong), KeyType.Identity }, { typeof(ulong?), KeyType.Identity },
                                                 { typeof(BigInteger), KeyType.Identity }, { typeof(BigInteger?), KeyType.Identity },
                                                 { typeof(Guid), KeyType.Guid }, { typeof(Guid?), KeyType.Guid },
                                             };

            Properties = new List<IPropertyMap>();
            Table(typeof(T).Name);
        }

        protected Dictionary<Type, KeyType> PropertyTypeKeyTypeMapping { get; }

        /// <summary>
        ///   处理表结构
        /// </summary>
        /// <param name="schemaName"></param>
        public virtual void Schema(string schemaName)
        {
            if (!string.IsNullOrWhiteSpace(schemaName))
                SchemaName = schemaName;
        }

        /// <summary>
        /// 设置表名
        /// </summary>
        /// <param name="tableName"></param>
        public virtual void Table(string tableName)
        {
            TableName = tableName;
        }

        /// <summary>
        /// 自动映射
        /// </summary>
        protected virtual void AutoMap()
        {
            AutoMap(null);
        }

        /// <summary>
        ///  移动映射
        /// </summary>
        /// <param name="canMap"></param>
        protected virtual void AutoMap(Func<Type, PropertyInfo, bool> canMap)
        {
            var type = typeof(T);

            //获取属性
            var tableAttributeMembers = EntityType.GetCustomAttributes(typeof(TableAttribute), true);

            var tableAttribute = (TableAttribute)tableAttributeMembers.SingleOrDefault();
            if (tableAttribute == null)
            {
                return;
            }

            //设置表名和结构名称
            Table(tableAttribute.TableName);
            Schema(tableAttribute.SchemaName);

            //判断是否定义key对象
            var hasDefinedKey = Properties.Any(p => p.KeyType != KeyType.NotAKey);
            PropertyMap keyMap = null;

            //获取所有属性的PropertyAttribute成员集合对象
            var customPropertyAttributeMembers = type.GetProperties()
                .SelectMany(s =>
                {
                    var propertyAttribute = (PropertyAttribute)s.GetCustomAttributes(typeof(PropertyAttribute), false).SingleOrDefault();

                    if (propertyAttribute != null)
                        return new Dictionary<string, PropertyAttribute>
                        {
                            {s.Name, propertyAttribute}
                        };

                    var propertyAttributes = new Dictionary<string, PropertyAttribute>
                    {
                        {s.Name, null}
                    };
                    return propertyAttributes;
                }).ToList();

            //遍历属性
            foreach (var propertyInfo in type.GetProperties())
            {
                if (Properties.Any(p => p.Name.Equals(propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase)))
                {
                    continue;
                }

                if (canMap != null && !canMap(type, propertyInfo))
                {
                    continue;
                }

                //获取属性对应的特性标签
                var propertyInfoOfAttribute = customPropertyAttributeMembers.SingleOrDefault(x => x.Key == propertyInfo.Name).Value;

                if (propertyInfoOfAttribute == null)
                    continue;

                if (propertyInfoOfAttribute.Ignored)
                    continue;

                var map = Map(propertyInfo, propertyInfoOfAttribute.ColumnName);

                //如果没有定义key的话就寻找有关键字为key的属性
                if (hasDefinedKey) continue;

                //并获取特性标签有主键类型的
                if (propertyInfoOfAttribute.KeyType != KeyType.NotAKey)
                {
                    keyMap = map;
                }
                else if (string.Equals(map.PropertyInfo.Name, "id", StringComparison.InvariantCultureIgnoreCase))
                {
                    keyMap = map;
                }
                else if (keyMap == null && map.PropertyInfo.Name.EndsWith("id", true, CultureInfo.InvariantCulture))
                {
                    keyMap = map;
                }
            }

            keyMap?.Key(PropertyTypeKeyTypeMapping.ContainsKey(keyMap.PropertyInfo.PropertyType)
                ? PropertyTypeKeyTypeMapping[keyMap.PropertyInfo.PropertyType]
                : KeyType.Assigned);
        }

        /// <summary>
        /// 流畅地将实体属性映射到列
        /// </summary>
        protected PropertyMap Map(Expression<Func<T, object>> expression)
        {
            var propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
            return Map(propertyInfo);
        }

        /// <summary>
        /// 流畅地将实体属性映射到列
        /// </summary>
        protected PropertyMap Map(PropertyInfo propertyInfo, string cusotmColumnName = null)
        {
            var result = new PropertyMap(propertyInfo, cusotmColumnName);
            GuardForDuplicatePropertyMap(result);
            Properties.Add(result);
            return result;
        }

        /// <summary>
        /// 从实体中移除某个属性
        /// </summary>
        /// <param name="expression"></param>
        protected void UnMap(Expression<Func<T, object>> expression)
        {
            var propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
            var mapping = Properties.SingleOrDefault(w => propertyInfo != null && w.Name == propertyInfo.Name);

            if (mapping == null)
            {
                throw new ApplicationException("无法映射，因为映射不存在");
            }

            Properties.Remove(mapping);
        }

        /// <summary>
        /// 重复属性保护
        /// </summary>
        /// <param name="result"></param>
        private void GuardForDuplicatePropertyMap(PropertyMap result)
        {
            if (Properties.Any(p => p.Name.Equals(result.Name)))
            {
                throw new ArgumentException(string.Format("检测到重复的属性名字 {0} .", result.Name));
            }
        }
    }
}